package br.com.hs.nfe.common.validation;

import br.com.hs.nfe.common.anotation.AnotationConstants;
import br.com.hs.nfe.common.anotation.DocumentTypeEnum;
import br.com.hs.nfe.common.anotation.Element;
import br.com.hs.nfe.common.anotation.Node;
import br.com.hs.nfe.common.to.BaseTO;
import br.com.hs.nfe.common.util.HSConstants;
import br.com.hs.nfe.common.util.HSMessageConstants;
import br.com.hs.nfe.common.util.StringHelper;
import br.com.hs.nfe.common.validation.exception.HSValidationException;
import br.com.hs.nfe.util.UtilHelper;
import br.com.hs.nfe.util.exception.HSUtilException;
import java.lang.reflect.Field;
import java.text.MessageFormat;
import java.util.List;

/**
 * Classe responsável por efetuar as validações das anotações.
 * @author Ranlive Hrysyk
 */
public class ValidadorAnotacoes {

    /**
     * Valida das anotações nas classes BaseTO.
     */
    public static void validate(Validable<BaseTO> object) throws HSValidationException {
        try {
            BaseTO baseTO = (BaseTO) object;
            String className = "";
            if (baseTO.getClass().isAnnotationPresent(Node.class)) {
                Node node = baseTO.getClass().getAnnotation(Node.class);
                className = MessageFormat.format(HSConstants.APP_CLASS_NAME, new Object[]{node.name()});
            }

            for (Field field : baseTO.getClass().getDeclaredFields()) {
                //Torna o campo acessível
                field.setAccessible(true);
                //Obtém o valor do campo.
                Object value = field.get(object);

                if (field.isAnnotationPresent(Element.class)) {
                    Element element = field.getAnnotation(Element.class);
                    //Verifica se o campo é requerido
                    if ((value == null) && (element.required())) {
                        baseTO.addValidationError(MessageFormat.format(HSMessageConstants.MSG_CAMPO_OBRIGATORIO, new Object[]{element.publicName(), className}));
                    } else if (value != null) {
                        switch (element.type().ordinal()) {
                            case 0: //CARACTER
                                String caracterValue = ((String) value).trim();
                                int tamanhoCampo = caracterValue.length();
                                //Valida o tamanho do campo
                                if ((element.min() == element.max()) && (tamanhoCampo != element.min()) && ((element.min() != AnotationConstants.CONSTANT_NUMERIC) && (element.max() != AnotationConstants.CONSTANT_NUMERIC))) {
                                    baseTO.addValidationError(MessageFormat.format(HSMessageConstants.MSG_CAMPO_TAMANHO_FIXO, new Object[]{element.publicName(), element.min(), className}));
                                } else if ((tamanhoCampo < element.min() && (element.min() != AnotationConstants.CONSTANT_NUMERIC)) || (tamanhoCampo > element.max()) && (element.max() != AnotationConstants.CONSTANT_NUMERIC)) {
                                    baseTO.addValidationError(MessageFormat.format(HSMessageConstants.MSG_CAMPO_TAMANHO_INVALIDO, new Object[]{element.publicName(), element.min(), element.max(), className}));
                                }
                                //Valida o formato do campo
                                if ((!StringHelper.isBlankOrNull(element.formart())) && (!element.formart().equals(AnotationConstants.CONSTANT_UNFORMATED)) && (!caracterValue.matches(element.formart()))) {
                                    baseTO.addValidationError(MessageFormat.format(HSMessageConstants.MSG_CAMPO_INVALIDO, new Object[]{element.publicName(), className}));
                                } else if (!element.document().equals(DocumentTypeEnum.UNVALIDABLE)) {
                                    validateDocument(className, element, value, baseTO);
                                }
                                break;
                            case 1: //NUMBER
                                String numberValue = ((String) value).trim();
                                StringBuilder format = new StringBuilder();
                                format.append("[0-9]{").append((element.min() >= AnotationConstants.CONSTANT_NUMERIC ? element.min() : "0")).append(",").append(element.max()).append("}");
                                if (!numberValue.matches(format.toString())) {
                                    baseTO.addValidationError(MessageFormat.format(HSMessageConstants.MSG_CAMPO_INVALIDO, new Object[]{element.publicName(), className}));
                                } else if (!element.document().equals(DocumentTypeEnum.UNVALIDABLE)) {
                                    validateDocument(className, element, value, baseTO);
                                }
                                break;
                            case 3: //ENUM
                                if ((value instanceof String) && (!element.document().equals(DocumentTypeEnum.UNVALIDABLE))) {
                                    validateDocument(className, element, value, baseTO);
                                }
                                break;
                            case 5: //NODE                              
                                if ((value instanceof Validable) && (value instanceof BaseTO)) {
                                    BaseTO baseTOValue = (BaseTO) value;
                                    validate((Validable) baseTOValue);
                                    if (baseTOValue.hasValidationError()) {
                                        for (String error : baseTOValue.getValidationErrorList()) {
                                            if (error.startsWith("[")) {
                                                int index = className.lastIndexOf("]");
                                                error = className.substring(0, index) + "/" + error.substring(1);
                                            }
                                            baseTO.addValidationError(error);
                                        }
                                        baseTOValue.clearValidationError();
                                    }
                                }
                                break;
                            case 6: //ARRAY
                                if (value instanceof List) {
                                    List<Object> list = (List<Object>) value;
                                    for (Object obj : list) {
                                        if ((obj instanceof Validable) && (obj instanceof BaseTO)) {
                                            BaseTO baseTOValue = (BaseTO) obj;
                                            validate((Validable) baseTOValue);
                                            if (baseTOValue.hasValidationError()) {
                                                for (String error : baseTOValue.getValidationErrorList()) {
                                                    if (error.startsWith("[")) {
                                                        int index = className.lastIndexOf("]");
                                                        error = className.substring(0, index) + "/" + error.substring(1);
                                                    }
                                                    baseTO.addValidationError(error);
                                                }
                                                baseTOValue.clearValidationError();
                                            }
                                        }
                                    }
                                }
                                break;
                        }
                    }
                }
            }
        } catch (Exception ex) {
            throw new HSValidationException(ex.getMessage(), ex);
        }
    }

    /**
     * Valida os tipos de documento.
     */
    private static void validateDocument(String className, Element element, Object value, BaseTO baseTO) throws HSUtilException {
        boolean isValid = true;
        String documentValue = value.toString();

        switch (element.document().ordinal()) {
            case 1: //CNPJ                            
            case 2: //CPF
                if (!ValidadorDocumento.validateDocument(documentValue)) {
                    isValid = false;
                }
                break;
            case 3: //Sigla da UF
                if (!UtilHelper.getInstance().validarUF(documentValue)) {
                    isValid = false;
                }
                break;
            case 4: //Código da UF                
                if (!UtilHelper.getInstance().validarCodigoUF(documentValue)) {
                    isValid = false;
                }
                break;
            case 5: //Código do município
                if (!UtilHelper.getInstance().validarCodigoMunicipio(documentValue)) {
                    isValid = false;
                }
                break;
            case 6: //Código do país
                if (!UtilHelper.getInstance().validarCodigoPais(documentValue)) {
                    isValid = false;
                }
                break;
            case 7:
                if (!UtilHelper.getInstance().validarCFOP(documentValue)) {
                    isValid = false;
                }
                break;
        }

        if (!isValid) {
            baseTO.addValidationError(MessageFormat.format(HSMessageConstants.MSG_X_Y_INVALIDO, new Object[]{element.publicName(), documentValue, className}));
        }
    }
}
